Runtime (Effect)
https://effect.website/docs/runtime/
https://effect-ts.github.io/effect/effect/ManagedRuntime.ts.html
gpt-5.icon
Runtime<R>は主に3つの構成要素からなる
Context<R>
R型の依存関係を持つ環境(DIコンテナ的なもの)
FiberRefs
Fiberごとの状態管理用
RuntimeFlags
中断や協調的スケジューリングなどの設定
⚙️ Runtime System の役割
Effect プログラムを「実行」するのが Runtime System です。
Effect は「実行されるべき手続き(木構造)」を表すだけで、実際の副作用(ログ出力、IO、非同期処理など)を起こすのは Runtime です。
実行の流れ:
code:_
Effect<A, E, R> + Context<R>
↓
Runtime System が実行
↓
Exit<A, E> (結果 or エラー)
Runtime は内部で root fiber(最初のスレッド)を作り、
Effect の「手順」をステップごとに実行していきます。
Runtime.defaultRuntime
Effect.runPromise() や Effect.runFork() を呼び出すと、内部的に デフォルトの Runtime が使われる
つまり、下記2つは等価
code:ts
Effect.runPromise(program)
code:ts
Runtime.runPromise(Runtime.defaultRuntime)(program)
このデフォルトランタイムには以下が含まれます:
空の Context
デフォルトの FiberRefs(標準サービスを含む)
Interruption と CooperativeYielding が有効な RuntimeFlags
→ 通常の開発ではデフォルトランタイムで十分です。
🧱 Locally Scoped Runtime Configuration(局所的な設定上書き)
Effect では、実行時設定(logger, clock, random など)は親 workflow の Runtime 設定を 継承 します。
しかし、一部だけ別設定にしたい場合があります。
🔹 Effect.provide による局所的上書き
code:ts
Effect.provide(addSimpleLogger)
を使うと、そのブロック内だけ新しい設定(Layerなど)を適用できます。
code:ts
yield* Effect.log("Application started!") // default logger
yield* Effect.log("Custom logger!").pipe(Effect.provide(addSimpleLogger))
実行結果は:
code:_
timestamp=... Application started!
'Custom logger!'
スコープを抜けると元の設定に戻ります。
🏗️ ManagedRuntime(トップレベルでのカスタムランタイム)
ManagedRuntime.make
Layerを1つの Runtime に変換する
アプリケーション全体で使うカスタムランタイムを構築する方法です。
code:ts
const appLayer = Logger.replace(Logger.defaultLogger,
Logger.make(({ message }) => console.log(message))
)
const runtime = ManagedRuntime.make(appLayer)
runtime.runSync(Effect.log("Application started!"))
runtime.dispose()
React やサーバー環境など「メイン関数が直接書けない」場合でも、この ManagedRuntime を使えば効果的に依存を注入・管理できます。
🔗 フレームワーク統合(React など)
React のように「外部フレームワーク主導」の環境では、ManagedRuntime が便利です。
code:ts
class Notifications extends Effect.Tag("Notifications")<
Notifications,
{ readonly notify: (msg: string) => Effect.Effect<void> }
() {
static Live = Layer.succeed(this, { notify: Console.log })
}
async function main() {
const runtime = ManagedRuntime.make(Notifications.Live)
await runtime.runPromise(Notifications.notify("Hello, world!"))
await runtime.dispose()
}
この部分は 「Effect がアプリ全体のメインループを持たない場合にどうやって Runtime を扱うか」 という話です。
React や Next.js のようなフレームワークでは、アプリの実行フローを自分で完全にはコントロールできない ため、ManagedRuntime が必要になるんです。
🧭 背景:なぜ外部フレームワークでは特殊な対応が必要なのか?
Effect は本来、次のように「自分で実行する」世界を想定しています:
code:ts
import { Effect } from "effect"
const program = Effect.log("Hello!")
Effect.runPromise(program)
ここでは Effect.runPromise が「プログラムの入り口」になっており、
Effect がアプリの実行全体を支配しています。
一方、React のような「外部フレームワーク主導」の環境では状況が違います。
React では:
あなたが書く関数(コンポーネントやハンドラ)は React によって呼ばれる
React がライフサイクルを制御している(mount/unmount など)
したがって「main 関数」や「アプリのエントリーポイント」を自分で作れない
つまり、Effect の Effect.run*() のように「自分でプログラムを起動する」タイミングがないのです。
🧩 解決策:ManagedRuntime を使って「外から」Effectを実行できるようにする
ManagedRuntime は、Effect の実行環境(Runtime)を一度初期化し、必要なときに再利用できるようにする 仕組みです。
これを使うと、React のような環境でも
「Effect の世界(依存解決・Logger・Context管理)」を維持しながら
任意のタイミングで Effect を実行できます。
🔹 例:React 環境で使うイメージ
code:ts
import { Effect, ManagedRuntime, Layer, Console } from "effect"
// 1️⃣ 通知サービスを定義
class Notifications extends Effect.Tag("Notifications")<
Notifications,
{ readonly notify: (message: string) => Effect.Effect<void> }
() {
// 2️⃣ 実装(Layer)を用意
static Live = Layer.succeed(this, {
notify: (message) => Console.log([通知] ${message})
})
}
// 3️⃣ アプリ起動時に runtime を作成
const runtime = ManagedRuntime.make(Notifications.Live)
// 4️⃣ Reactコンポーネント内で runtime を使って Effect を実行
function Button() {
const handleClick = async () => {
await runtime.runPromise(Notifications.notify("ボタンが押されました!"))
}
return <button onClick={handleClick}>Click me</button>
}
// 5️⃣ アプリ終了時にリソースを解放
window.addEventListener("beforeunload", () => {
runtime.dispose()
})
🔍 ここでのポイント
table:_
概念 役割
Layer サービス(依存関係)の定義と提供(DIコンテナ的なもの)
ManagedRuntime.make(layer) Layer から実行環境(Runtime)を生成する
runtime.runPromise(effect) 生成した Runtime 上で Effect を実行する
runtime.dispose() Runtime が使っていたリソースを解放する(例:DB接続など)
つまり:
アプリ起動時に一度だけ Runtime を作る
React の中では runtime を再利用して Effect を走らせる
アプリ終了時に dispose してクリーンアップする
これが「外部フレームワーク主導環境における ManagedRuntime の役割」です。
💡 なぜこれが便利なのか?
React, Express, Fastify, Next.js などは Effect が制御できない外側の世界 を持っています。
それでも:
Effect の DI (Layer) やサービス(Logger, Database, Configなど)
Fiber の管理(非同期処理の中断やエラーハンドリング)
を活用したい場合があります。
そのために ManagedRuntime を使うと、
Effect の恩恵を部分的に持ち込める のです。